﻿# coding: utf-8

# Copyright (c) P SOFTHOUSE. All rights reserved.

import os
# import site
import sys
# from collections import defaultdict
from functools import partial

import maya.cmds as cmds  # pylint: disable=E0401
import maya.mel as mel  # pylint: disable=E0401
import maya.utils as utils  # pylint: disable=E0401
from maya import OpenMayaUI  # pylint: disable=E0401
import PencilAddItemsDialog as AddItemsDialog
from PencilLineGuiUtils import getLocalizedString, runDeferred, sortStrings, ListItemDelegate, RenderElementDelegate
# Using Qt.py for backward compatibility with PySide
# (used by Maya 2016.5 and older).
try:
    from PSOFT.vendor.Qt_py.Qt import QtCore, QtGui, QtWidgets, QtCompat

    # Some classes moved with Maya 2017+ (Qt-5?)
    try:
        from PSOFT.vendor.Qt_py.Qt.QtCore import QItemSelection, QItemSelectionModel, QItemSelectionRange
        from PSOFT.vendor.Qt_py.Qt.QtWidgets import QApplication, QStyleFactory
    except ImportError:
        from PSOFT.vendor.Qt_py.Qt.QtGui import QItemSelection, QItemSelectionModel, QItemSelectionRange, QApplication, QStyleFactory

    try:
        from shiboken import wrapInstance
    except ImportError:
        from shiboken2 import wrapInstance
except ImportError:
    pass

CURRENT_PATH = os.path.dirname(__file__)


class undoBlock(object):
    def __enter__(self):
        cmds.undoInfo(openChunk=True)
        return self

    def __exit__(self, exception_type, exception_value, traceback):
        cmds.undoInfo(closeChunk=True)
        return False


class DestroyGUI_inDestructor(object):
    def __del__(self):
        SingletonType.doDestroy(GUI)


# relaoad()した際に既存オブジェクトを破棄する
__destroyer = DestroyGUI_inDestructor()


# シングルトンの実装
class SingletonType(type):
    __instances = {}

    def __init__(cls, name, bases, dct):
        if 'doDestroy' not in dct:
            dct['doDestroy'] = lambda: None
        super(SingletonType, cls).__init__(name, bases, dct)

    def __call__(cls, *args, **kwargs):
        if cls not in cls.__instances:
            cls.__instances[cls] = super(
                SingletonType, cls).__call__(*args, **kwargs)
        return cls.__instances[cls]

    def __del__(self):
        print 'Pencil GUI.__del__'

    @classmethod
    def doDestroy(cls, type_):
        if type_ not in cls.__instances:
            return
        cls.__instances[type_].doDestroy()
        del cls.__instances[type_]


class metaSingletonGUI(SingletonType, type(QtWidgets.QMainWindow)):
    pass


# ----------------------------------------------------------------------------
# GUIを作るクラス
class GUI(QtWidgets.QMainWindow):
    __metaclass__ = metaSingletonGUI

    __selectionJobNb = -1
    # リストビューのモデル
    # __model = QtGui.QStringListModel()
    __model = QtGui.QStandardItemModel()
    _RENDERPRIORITY_ROLE = QtCore.Qt.UserRole + 1
    _VIEWORDER_ROLE = QtCore.Qt.UserRole + 2
    _MODIFIED_ROLE = QtCore.Qt.UserRole + 3
    __model.setSortRole(_RENDERPRIORITY_ROLE)

    # レンダーエレメントのモデル
    __renderElementModel = QtGui.QStandardItemModel(0, 2)

    # Selected Groupsのモデル
    __groupsModel = QtCore.QStringListModel()

    _MERGE_GROUP_ATTRIBUTE_LONG_NAME = 'PencilMergeGroupTarget'
    _MERGE_GROUP_ATTRIBUTE_SHORT_NAME = 'pmgt'
    _MERGE_GROUP_ATTRIBUTE_TYPE = 'bool'

    # IDs of the tabs
    class TAB_ID:
        LINE_LIST = 0
        MERGE_GROUPS = 1
        RENDER_ELEMENTS = 2

    def onSelectionChanged(self):
        self.lineApplyMayaSelection()
        #self.groupsApplyMayaSelection()
        self.adaptRefreshGroupsModel()
        self.elemApplyMayaSelection()

    def doDestroy(self):
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
        self.close()
        if self.__selectionJobNb >= 0:
            cmds.scriptJob(kill=self.__selectionJobNb)
            self.__selectionJobNb = -1

    # コンストラクタ
    def __init__(self, parent):
        # if self._SingletonGUI__isInitialized:
        #     return

        super(GUI, self).__init__(parent)
        # self._SingletonGUI__isInitialized = True

        uiFilePath = os.path.join(CURRENT_PATH, "LineList.ui")
        # 日本語パスにインストールされることもあるのでユニコードに変換
        self.UI = QtCompat.loadUi(uiFilePath.decode(sys.getfilesystemencoding()))

        # We add a script job to be notified when the selection changes in Maya
        # to be able to synchronize the selected lines in our view.

        if self.__selectionJobNb == -1:
            self.__selectionJobNb = cmds.scriptJob(
                event=["SelectionChanged", self.onSelectionChanged])

        self.setWindowTitle(getLocalizedString("kLineWindowWindowTitle"))
        self.setWindowFlags(QtCore.Qt.Window
                            | QtCore.Qt.WindowTitleHint
                            | QtCore.Qt.WindowCloseButtonHint)

        # UIのローカライズ
        self.UI.addButton.setText(getLocalizedString("kLineWindowLabelAdd"))
        self.UI.deleteButton.setText(getLocalizedString("kLineWindowLabelDelete"))
        self.UI.renderPriorityLabel.setText(
            getLocalizedString("kLineWindowLabelRenderPriority"))
        self.UI.lineNameLabel.setText(
            getLocalizedString("kLineWindowLabelLineName"))
        self.UI.groupsAddButton.setText(
            getLocalizedString("kLineWindowLabelAdd"))
        self.UI.groupsRemoveButton.setText(
            getLocalizedString("kLineWindowLabelRemove"))
        self.UI.elemAddButton.setText(
            getLocalizedString("kLineWindowLabelAddLine"))
        self.UI.elemEpsAddButton.setText(
            getLocalizedString("kLineWindowLabelAddEPS"))
        self.UI.elemPldAddButton.setText(
            getLocalizedString("kLineWindowLabelAddPLD"))
        self.UI.elemDeleteButton.setText(
            getLocalizedString("kLineWindowLabelDelete"))
        self.UI.utilityLabel.setText(
            getLocalizedString("kLineWindowLabelUtility"))
        self.UI.separateFoldersCheckBox.setText(
            getLocalizedString("kLineWindowLabelSeparateFolders"))
        self.UI.separatorLabel.setText(
            getLocalizedString("kLineWindowLabelSeparator"))
        self.UI.setToAllNodesButton.setText(
            getLocalizedString("kLineWindowLabelSetToAllNodes"))
        self.UI.tabWidget.setTabText(
            0, getLocalizedString("kLineWindowTabText0"))
        self.UI.tabWidget.setTabText(
            1, getLocalizedString("kLineWindowTabText1"))
        self.UI.tabWidget.setTabText(
            2, getLocalizedString("kLineWindowTabText2"))

        # UIのレイアウト調整
        self.setCentralWidget(self.UI)
        self.resize(310, 375)
        # self.UI.setContentsMargins(3, 3, 3, 3)
        pixmap_refresh = QtGui.QPixmap(":/refresh.png")
        pixmap_layerup = QtGui.QPixmap(":/moveLayerUp.png")
        pixmap_layerdown = QtGui.QPixmap(":/moveLayerDown.png")

        try:
            uiscale = cmds.mayaDpiSetting(query=True, realScaleValue=True)

            # for type_ in {QtWidgets.QPushButton,
            #               QtWidgets.QLabel}:
            #     for wgt in _UI.findChildren(type_):
            #         wgt.setMinimumSize(wgt.minimumSize() * 2)
            def setminsize(wgt):
                wgt.setMinimumSize(wgt.minimumSize() * uiscale)
            setminsize(self.UI.addButton)
            setminsize(self.UI.deleteButton)
            setminsize(self.UI.renderPriorityLabel)
            setminsize(self.UI.lineNameLabel)
            setminsize(self.UI.groupsAddButton)
            setminsize(self.UI.groupsRemoveButton)
            setminsize(self.UI.elemAddButton)
            setminsize(self.UI.elemEpsAddButton)
            setminsize(self.UI.elemPldAddButton)
            setminsize(self.UI.elemDeleteButton)
            setminsize(self.UI.utilityLabel)
            setminsize(self.UI.separatorLabel)
            setminsize(self.UI.setToAllNodesButton)

            pixmap_refresh = pixmap_refresh.scaled(
                pixmap_refresh.size()*uiscale,
                QtCore.Qt.KeepAspectRatioByExpanding,
                QtCore.Qt.SmoothTransformation)
            if uiscale == 1.5:
                pixmap_layerup = QtGui.QPixmap(":/moveLayerUp_150.png")
                pixmap_layerdown = QtGui.QPixmap(":/moveLayerDown_150.png")
            elif uiscale == 2.0:
                pixmap_layerup = QtGui.QPixmap(":/moveLayerUp_200.png")
                pixmap_layerdown = QtGui.QPixmap(":/moveLayerDown_200.png")

        except Exception as e:
            print e
            pass

        buttonIcon=QtGui.QIcon(pixmap_refresh)
        self.UI.refreshButton.setIcon(buttonIcon)
        self.UI.groupsRefreshButton.setIcon(buttonIcon)
        self.UI.elemRefreshButton.setIcon(buttonIcon)

        self.UI.renderPriorityLabel.setFont("Tahoma")
        self.UI.lineNameLabel.setFont("Tahoma")

        self.__delegate=ListItemDelegate()
        self.__grpdelegate=ListItemDelegate()
        self.__elmdelegate=RenderElementDelegate()

        # lineListViewへのモデルの登録
        self.UI.lineListView.setModel(self.__model)
        self.UI.lineListView.setItemDelegate(self.__delegate)
        self.refreshModel(True)

        validator=QtGui.QIntValidator(0, 99)
        self.UI.renderPriorityEditBox.setValidator(validator)

        # Selected Groupsのモデルの登録
        self.UI.groupsListView.setModel(self.__groupsModel)
        self.UI.groupsListView.setItemDelegate(self.__grpdelegate)
        self.refreshGroupsModel(True)

        # レンダーエレメントのモデルの登録
        self.UI.elementsTreeView.setModel(self.__renderElementModel)
        self.UI.elementsTreeView.setItemDelegate(self.__elmdelegate)
        try:
            self.UI.elementsTreeView.header().setSectionResizeMode(
                                            0, QtWidgets.QHeaderView.Stretch)
            self.UI.elementsTreeView.header().setSectionResizeMode(
                                            1, QtWidgets.QHeaderView.Interactive)
        except AttributeError:
            self.UI.elementsTreeView.header().setResizeMode(
                                            0, QtWidgets.QHeaderView.Stretch)
            self.UI.elementsTreeView.header().setResizeMode(
                                            1, QtWidgets.QHeaderView.Interactive)

        self.UI.lineListView.setStyleSheet(
            'QListView:indicator:unchecked {background-color: #000000;}')
        self.UI.elementsTreeView.setStyleSheet(
            'QTreeView:indicator:unchecked {background-color: #000000;}')

        self.refreshElemModel(True)

        # スロットの登録
        self.UI.lineListView.clicked.connect(self.onLineClicked)

        # MEMO: 一度ローカル変数に代入しないと
        #       Maya2015・2016環境でクラッシュする
        selModel=self.UI.lineListView.selectionModel()
        selModel.currentChanged.connect(self.onCurrentChanged)

        self.__model.itemChanged.connect(self.onItemChanged)

        self.UI.addButton.clicked.connect(self.onAddButtonClicked)
        # self.UI.addButton.setIcon(QtGui.QIcon(QtGui.QPixmap(":/newLayerEmpty.png")))
        # self.UI.addButton.setIconSize(self.UI.addButton.size())
        self.UI.deleteButton.clicked.connect(self.onDeleteButtonClicked)
        # self.UI.deleteButton.setIcon(QtGui.QIcon(QtGui.QPixmap(":/deleteActive.png")))
        # self.UI.deleteButton.setIconSize(self.UI.addButton.size())


        self.UI.refreshButton.clicked.connect(self.onRefreshButtonClicked)
        self.UI.pbTop.clicked.connect(self.onTopButtonClicked)
        self.UI.pbFront.clicked.connect(self.onPrioUpButtonClicked)
        self.UI.pbBack.clicked.connect(self.onPrioDownButtonClicked)
        self.UI.pbBottom.clicked.connect(self.onBottomButtonClicked)

        self.UI.pbTop.setVisible(False)
        self.UI.pbFront.setText('')
        self.UI.pbFront.setIcon(QtGui.QIcon(pixmap_layerup))
        self.UI.pbFront.setIconSize(pixmap_layerup.size())
        self.UI.pbFront.setToolTip(getLocalizedString("kLineSetsUp"))

        self.UI.pbBack.setText('')
        self.UI.pbBack.setIcon(QtGui.QIcon(pixmap_layerdown))
        self.UI.pbBack.setIconSize(pixmap_layerdown.size())
        self.UI.pbBack.setToolTip(getLocalizedString("kLineSetsDown"))
        self.UI.pbBottom.setVisible(False)

        self.UI.renderPriorityEditBox.editingFinished.connect(
            self.onPriorityEditboxEdited)
        self.UI.renderPrioritySlider.valueChanged.connect(
            self.onPrioritySliderValueChanged)
        self.UI.renderPrioritySlider.sliderReleased.connect(
            self.onPrioritySliderReleased)

        self.UI.lineNameEditBox.setVisible(False)
        self.UI.lineNameLabel.setVisible(False)

        self.clearAttributeUI()

        self.UI.groupsAddButton.clicked.connect(self.onGroupsAddButtonClicked)
        self.UI.groupsRemoveButton.clicked.connect(self.onGroupsRemoveButtonClicked)
        self.UI.groupsRefreshButton.clicked.connect(self.onGroupsRefreshButtonClicked)

        # Setup render elements
        self.UI.elementsTreeView.clicked.connect(self.onElemClicked)

        elemSelModel = self.UI.elementsTreeView.selectionModel()
        elemSelModel.currentChanged.connect(self.onElemCurrentChanged)

        self.__renderElementModel.itemChanged.connect(self.onElemItemChanged)

        self.UI.elemAddButton.clicked.connect(self.onElemAddButtonClicked)
        self.UI.elemEpsAddButton.clicked.connect(self.onEpsAddButtonClicked)
        self.UI.elemPldAddButton.clicked.connect(self.onPldAddButtonClicked)
        self.UI.elemDeleteButton.clicked.connect(
            self.onElemDeleteButtonClicked)
        self.UI.elemRefreshButton.clicked.connect(
            self.onElemRefreshButtonClicked)

        self.UI.separateFoldersCheckBox.stateChanged.connect(
            self.onSeparateFoldersCheckBoxStateChanged)
        self.UI.setToAllNodesButton.clicked.connect(
            self.onSetToAllNodesButtonClicked)

        self.UI.tabWidget.currentChanged.connect(self.onChange)

        # Set the focus to the line list when the window is first displayed
        self.UI.lineListView.setFocus(QtCore.Qt.OtherFocusReason)

        # Disable in demo version
        if mel.eval("pcl4license -mode") == "Demo":
            self.UI.elemEpsAddButton.setEnabled(False)
            self.UI.elemPldAddButton.setEnabled(False)


    def onChange(self, i):
        # Refresh the list of the tab where we moved.
        if i == self.TAB_ID.LINE_LIST:
            self.onRefreshButtonClicked()
        elif i == self.TAB_ID.MERGE_GROUPS:
            self.onGroupsRefreshButtonClicked()
        elif i == self.TAB_ID.RENDER_ELEMENTS:
            self.onElemRefreshButtonClicked()
        else:
            msg='Unknown tab. Number=' + str(i)
            cmds.error(msg)

    # 指定されたノードのアトリビュートの値を
    # アトリビュート設定用のUIに反映させる
    def refreshAttributeUI(self, nodeName):
        if not nodeName:
            return

        # active = cmds.getAttr(nodeName + ".active")

        priority=cmds.getAttr(nodeName + ".renderPriority")
        self.UI.renderPriorityEditBox.setText(str(priority))
        self.UI.renderPrioritySlider.setValue(priority)

        # ノードのアトリビュートエディタを開く
        # case 1. The call to showEditor changes window focus to the attribute editor, which is annoying.
        # mel.eval(u'evalDeferred("if(`objExists {0}`) {{if (!isAttributeEditorRaised()) showEditor {0}; else showEditorNoPopup {0};}}")'.format(nodeName))
        # case 2. The following might be better. By selecting the node instead of using showEditor, we don't
        # lose focus of the list.
        # mel.eval('evalDeferred("if(`objExists {0}`) select -r {0};")'.format(nodeName))
        # case 3. showEditorコマンドを呼ぶことでアトリビュートエディタを表示します。（case 1の修正パターン）
        # ただし、アトリビュートエディタが非アクティブ状態だと正しくノードが表示されないことがあるので、あえてupdateAEの呼び出しも行います。
        # 結果的にupdateAEが2回呼び出されることになる
        # mel.eval(u'evalDeferred("if(`objExists {0}`) {{ showEditor {0}; evalDeferred(\\"updateAE {0}\\"); }}")'.format(nodeName))

        cmds.evalDeferred(
            '''import maya; maya.mel.eval(\'''
                if(`objExists {0}`) {{
                    if (!isAttributeEditorRaised()) showEditor {0};
                    else showEditorNoPopup {0};
                }}\''')
            '''.format(nodeName))

    # アトリビュート設定用のUIに初期値を設定する
    def clearAttributeUI(self):
        self.UI.renderPriorityEditBox.setText("0")
        self.UI.renderPrioritySlider.setValue(0)

    # Returns the line among the current selection that is the active line
    @staticmethod
    def getCurrentMainSelection(view):
        index=view.selectionModel().currentIndex()
        if index.isValid():
            if isinstance(view.model(), QtCore.QStringListModel):
                return index.data(QtCore.Qt.DisplayRole)
            else:
                return view.model().itemFromIndex(
                            view.model().index(index.row(), 0)).text()
        else:
            return None

    # 現在選択状態のラインノードを取得する
    @staticmethod
    def getCurrentSelectedData(view):
        model=view.model()
        if isinstance(model, QtCore.QStringListModel):
            return [i.data(QtCore.Qt.DisplayRole) for i in view.selectedIndexes()]
        else:
            return [model.itemFromIndex(index).text()
                    for index in view.selectedIndexes() if index.column() == 0]

    # 指定されたノードをラインリスト上で選択状態にする
    @staticmethod
    def selectDatas(view, lineTexts):
        model = view.model()
        if isinstance(model, QtCore.QStringListModel):

            def str_index(strings, str):
                try:
                    return strings.index(t)
                except:
                    return -1

            strlist = model.stringList()
            int_indexes = [str_index(strlist, t) for t in lineTexts]
            indexes = [model.createIndex(i, 0) for i in int_indexes if i >= 0]
        else:
            indexes = [model.indexFromItem(item) for t in lineTexts for item in model.findItems(t, QtCore.Qt.MatchExactly, 0)]

        if indexes:
            selection = QItemSelection()
            for index in indexes:
                selection.append(QItemSelectionRange(index))

            view.selectionModel().select(selection,
                                         QItemSelectionModel.ClearAndSelect |
                                         QItemSelectionModel.Rows)
            view.scrollTo(indexes[0])

    # Returns the QIndex matching the specified line text.
    @staticmethod
    def getIndexFromData(view, lineText):
        model = view.model()
        if isinstance(model, QtCore.QStringListModel):
            try:
                index = model.stringList().index(lineText)
                return model.createIndex(index, 0)
            except:
                pass
        else:
            items = model.findItems(lineText, QtCore.Qt.MatchExactly, 0)
            if len(items) > 0:
                return model.indexFromItem(items[0])

        return None

    # Makes the specified line the current index.
    # Does not touch the selection state.
    @staticmethod
    def setCurrentLine(view, lineText):
        index = GUI.getIndexFromData(view, lineText)
        if index:
            view.scrollTo(index)
            view.selectionModel().setCurrentIndex(index, QItemSelectionModel.Current)

    # Selects only the specified line and makes it the main selection (current index)
    @staticmethod
    def setCurrentLineAndSelectIt(view, lineText):
        index = GUI.getIndexFromData(view, lineText)
        if index:
            view.selectionModel().select(index, QItemSelectionModel.ClearAndSelect)
            view.scrollTo(index)
            view.selectionModel().setCurrentIndex(index, QItemSelectionModel.Current)

    # 表示中のタブを更新する
    def refreshList(self):
        tabIndex = self.UI.tabWidget.currentIndex()

        if tabIndex == self.TAB_ID.LINE_LIST:
            self.refreshModel(True)
        elif tabIndex == self.TAB_ID.MERGE_GROUPS:
            self.refreshGroupsModel(True)
        elif tabIndex == self.TAB_ID.RENDER_ELEMENTS:
            self.refreshElemModel(True)

    # Set the selected lines and active line in the list.
    # Call using PencilLineGuiUtils.runDeferred to override
    # default selection behaviour.
    # Params:
    #   view: The view to update
    #   selectedNodes: The text of the lines to be selected
    #   activeIndex: The index to make active
    def fixSelection(self, view, selectedNodes, activeIndex):
        GUI.selectDatas(view, selectedNodes)
        view.selectionModel().setCurrentIndex(activeIndex,
                                              QItemSelectionModel.Current)

    ###
    # ------------------------------------------------------------------------------
    # Lines
    # ------------------------------------------------------------------------------

    # Returns the Line nodes in the scene.
    # If selectedOnly is True, only the nodes selected in the Maya UI
    # are returned.
    # Order is the order from the command 'ls'.
    @staticmethod
    def lineGetNodes(selectedOnly=False):
        return cmds.ls(selection=selectedOnly, type='PencilLine')

    # リストビューを更新する
    def refreshModel(self, isRestoreSelection):
        # Save the selection state before clearing the UI
        selectedNodes = self.getCurrentSelectedData(self.UI.lineListView)
        mainSel = self.getCurrentMainSelection(self.UI.lineListView)

        # nodeList = sorted(GUI.lineGetNodes(False))
        nodeList = GUI.lineGetNodes(False)
        self.__model.removeRows(0, self.__model.rowCount())
        for nodeName in reversed(nodeList):
            isActive = cmds.getAttr(nodeName + ".active")
            priority = cmds.getAttr(nodeName + ".renderPriority")

            newItem = QtGui.QStandardItem(nodeName)
            newItem.setCheckable(True)
            newItem.setData(priority, self._RENDERPRIORITY_ROLE)
            if isActive:
                newItem.setCheckState(QtCore.Qt.Checked)
            else:
                newItem.setCheckState(QtCore.Qt.Unchecked)
            self.__model.appendRow(newItem)
        self.__model.sort(0, QtCore.Qt.DescendingOrder)

        if isRestoreSelection:

            cleanSel = []
            for node in nodeList:
                if node in selectedNodes:
                    cleanSel.append(node)
            if cleanSel:
                GUI.selectDatas(self.UI.lineListView, cleanSel)
            if mainSel:
                self.setCurrentLine(self.UI.lineListView, mainSel)

    # エディットボックスに入力されたレンダープライオリティを
    # アトリビュートに反映させる
    def commitRenderPriority(self):
        # nodes = self.getCurrentSelectedData(self.UI.lineListView)

        selectedNodes = self.getCurrentSelectedData(self.UI.lineListView)
        mainSel = self.getCurrentMainSelection(self.UI.lineListView)
        if not mainSel:
            return
        try:
            priority = int(self.UI.renderPriorityEditBox.text())
        except ValueError:
            cmds.error('Pencil+ Line Render Priority could not be set. Value error.')
            return

        offset = priority - cmds.getAttr('.'.join([mainSel, "renderPriority"]))
        with undoBlock():
            for node in selectedNodes:
                prio = cmds.getAttr('.'.join([node, "renderPriority"])) + offset
                prio = min(99, max(0, priority))
                cmds.setAttr('.'.join([node, "renderPriority"]), prio)

    # Applies the Maya selection to the Lines view.
    def lineApplyMayaSelection(self):
        nodes = GUI.lineGetNodes(True)
        GUI.selectDatas(self.UI.lineListView, nodes)
        if len(nodes) > 0:
            self.setCurrentLine(self.UI.lineListView, nodes[-1])

    def lineFixSelection(self, selectedNodes, activeIndex):
        self.fixSelection(self.UI.lineListView, selectedNodes, activeIndex)
        self.refreshAttributeUI(self.UI.lineListView.model().itemFromIndex(activeIndex).text())

    # ------------------------------------------------------------------------------
    # Slots

    # Called when a line is clicked in the line list
    def onLineClicked(self, index):
        # Make sure that the clicked line is displayed in the attribute editor.
        # This gets called AFTER the current index has been changed, so we can't check
        # for a change in the current index to avoid refreshing the attribute editor 2 times
        # (here and in onCurrentChanged).
        node = self.__model.data(index)
        self.refreshAttributeUI(node)

    # Called when an item in the list of lines changes (for instance, when checked / unchecked)
    def onItemChanged(self, item):
        node = item.text()
        # Find the new active value
        value = 1 if item.checkState() == QtCore.Qt.Checked else 0

        selectedNodes = self.getCurrentSelectedData(self.UI.lineListView)

        # Make the line that was checked / unchecked the active line
        activeIndex = self.__model.indexFromItem(item)

        if node in selectedNodes:
            # Apply the new state to every selected item
            try:
                with undoBlock():
                    for n in selectedNodes:
                        cmds.setAttr(n + ".active", value)
            except RuntimeError:
                pass
        else:
            # Apply the new state to the node and select it in the list
            cmds.setAttr(node + ".active", value)
            selectedNodes = [node]

        # The default selection behaviour when clicking checkboxes is strange.
        # We force a custom behaviour by re-selecting after the default selection completes.
        callback = partial(self.lineFixSelection, selectedNodes=selectedNodes, activeIndex=activeIndex)
        runDeferred(callback)

    def onCurrentChanged(self, current, prev):
        # Changes with clicks are also handled by onLineClicked(), but we
        # can't tell them apart from changes with the keyboard to avoid refreshing
        # the attribute editor twice.
        currentNode = self.__model.data(current)
        self.refreshAttributeUI(currentNode)

    # "Add Pencil+ 4 Line" ボタンのクリック
    def onAddButtonClicked(self):
        createdNode = mel.eval("shadingNode -asUtility PencilLine")

        top = self.__model.item(0)
        prio = 0 if not top else top.data(self._RENDERPRIORITY_ROLE) + 1
        prio = min(99, prio)
        cmds.setAttr(createdNode + ".renderPriority",  prio)
        self.refreshModel(False)
        self.setCurrentLineAndSelectIt(self.UI.lineListView, createdNode)

        existPencilLineSet = False
        if "PencilLine" in cmds.ls(set=True):
            existPencilLineSet = True
        else:
            sets = cmds.sets(name="PencilLine")
            if sets == "PencilLine":
                existPencilLineSet = True
            else:
                cmds.delete(sets)
        if existPencilLineSet:
            cmds.sets(createdNode, addElement="PencilLine")


    # "Delete ボタンのクリック"
    def onDeleteButtonClicked(self):
        nodesToDelete = [self.__model.itemFromIndex(index).text()
                         for index in self.UI.lineListView.selectedIndexes()]
        cmds.delete(nodesToDelete)
        self.refreshModel(False)

        if "PencilLine" in cmds.ls(set=True):
            if cmds.sets("PencilLine", q=True, size=True) == 0:
                cmds.delete("PencilLine")

        # self.UI.lineListView.selectionModel().select(index, QItemSelectionModel.Select)
        # self.UI.lineListView.scrollTo(index)
        # selectedNodes = self.UI.lineListView.selectedIndexes()
        # nodes = [node.data() for node in selectedNodes]
        # for node in nodes:
        #     if node:
        #         nodeList = self.__model.stringList()
        #         index = nodeList.index(node)
        #         if nodeList[-1] == node:
        #             index -= 1
        #             # MEMO: 最後に残っていたノードのアトリビュートエディタが表示されたままになる事があるため
        #             #       アトリビュートエディタを明示的に初期状態に戻す
        #             #       ('showEditor ""' コマンドでアトリビュートを初期状態にできる)
        #             mel.eval('evalDeferred("showEditor \\\"\\\"")')
        #         self.clearAttributeUI()
        #         cmds.delete(node)
        #         self.refreshModel(False)
        #         modelIndex = self.__model.createIndex(index, 0)
        #         self.UI.lineListView.selectionModel().select(modelIndex, QItemSelectionModel.Select)
        #         self.UI.lineListView.scrollTo(modelIndex)

    # 更新ボタンのクリック
    def onRefreshButtonClicked(self):
        self.refreshModel(True)
        selected = self.getCurrentSelectedData(self.UI.lineListView)
        if len(selected) > 0:
#            GUI.selectData(self.UI.lineListView, selected[0])
            pass
        else:
            self.clearAttributeUI()

    def offsetPriority(self, selectedNode, offset):
        for node in selectedNode:
            priority = node.data(self._RENDERPRIORITY_ROLE) + offset
            node.setData(max(0, min(99, priority)), self._RENDERPRIORITY_ROLE)

    def modelClone(self, model):
        cmodel = QtGui.QStandardItemModel()
        cmodel.setSortRole(self._VIEWORDER_ROLE)
        for i in range(model.rowCount()):
            citem = model.item(i).clone()
            citem.setData(citem.data(self._RENDERPRIORITY_ROLE)*100,
                          self._VIEWORDER_ROLE)
            citem.setData(False, self._MODIFIED_ROLE)
            cmodel.appendRow(citem)
        return cmodel

    def modelOptimizationDescent(self, model, modified=[], check=False):
        prev = 99

        for i in range(model.rowCount()):
            node = model.item(i)
            prio = node.data(self._RENDERPRIORITY_ROLE)
            nprio = min(prev, prio)
            if prio != nprio:
                node.setData(nprio, self._RENDERPRIORITY_ROLE)
                modified[node.text()] = nprio if not check else max(0,nprio)
                prev = nprio - 1
            else:
                prev = (nprio - 1) if node.data(self._MODIFIED_ROLE) else nprio

    def modelOptimizationAscent(self, model, modified=[], check=False):
        prev = 0
        for i in reversed(range(model.rowCount())):
            node = model.item(i)
            prio = node.data(self._RENDERPRIORITY_ROLE)
            nprio = max(prev, prio)
            if prio != nprio:
                node.setData(nprio, self._RENDERPRIORITY_ROLE)
                modified[node.text()] = nprio if not check else min(99,nprio)
                prev = nprio + 1
            else:
                prev = (nprio + 1) if node.data(self._MODIFIED_ROLE) else nprio


    def onTopButtonClicked(self):

        cmodel = self.modelClone(self.__model)
        selectedId = sorted([i.row() for i
                             in self.UI.lineListView.selectedIndexes()])
        selectedNode = [cmodel.item(i) for i in selectedId]

        if not selectedNode:
            return
        if (selectedId[0] == 0):
            return

        top = self.__model.item(0)
        offset = 1 + top.data(self._RENDERPRIORITY_ROLE) \
            - selectedNode[0].data(self._RENDERPRIORITY_ROLE)

        self.offsetPriority(selectedNode, offset)
        cmodel.sort(0, QtCore.Qt.DescendingOrder)

        with undoBlock():
            for node in selectedNode:
                priority = node.data(self._RENDERPRIORITY_ROLE)
                cmds.setAttr('.'.join([node.text(), "renderPriority"]), priority)

    def onFrontButtonClicked(self):
        cmodel = self.modelClone(self.__model)
        selectedId = sorted([i.row() for i
                             in self.UI.lineListView.selectedIndexes()])
        selectedNode = [cmodel.item(i) for i in selectedId]

        if not selectedNode:
            return
        if (selectedId[0] == 0):
            return

        up = self.__model.item(selectedId[0] - 1)
        offset = 1 + up.data(self._RENDERPRIORITY_ROLE) \
            - selectedNode[0].data(self._RENDERPRIORITY_ROLE)

        self.offsetPriority(selectedNode, offset)
        cmodel.sort(0, QtCore.Qt.DescendingOrder)

        with undoBlock():
            for node in selectedNode:
                priority = node.data(self._RENDERPRIORITY_ROLE)
                cmds.setAttr('.'.join([node.text(), "renderPriority"]), priority)

    def onBackButtonClicked(self):
        cmodel = self.modelClone(self.__model)
        selectedId = sorted([i.row() for i
                             in self.UI.lineListView.selectedIndexes()])
        selectedNode = [cmodel.item(i) for i in selectedId]
        if not selectedNode:
            return
        if selectedId[-1] == self.__model.rowCount()-1:
            return

        down = self.__model.item(selectedId[-1] + 1)
        offset = - 1 + down.data(self._RENDERPRIORITY_ROLE) \
                 - selectedNode[-1].data(self._RENDERPRIORITY_ROLE)

        self.offsetPriority(selectedNode, offset)
        cmodel.sort(0, QtCore.Qt.DescendingOrder)

        with undoBlock():
            for node in selectedNode:
                priority = node.data(self._RENDERPRIORITY_ROLE)
                cmds.setAttr('.'.join([node.text(), "renderPriority"]), priority)

    def onBottomButtonClicked(self):
        cmodel = self.modelClone(self.__model)
        selectedId = sorted([i.row() for i
                             in self.UI.lineListView.selectedIndexes()])
        selectedNode = [cmodel.item(i) for i in selectedId]

        if not selectedNode:
            return
        if selectedId[-1] == self.__model.rowCount()-1:
            return

        bottom = self.__model.item(self.__model.rowCount()-1)
        offset = - 1 + bottom.data(self._RENDERPRIORITY_ROLE) \
                 - selectedNode[-1].data(self._RENDERPRIORITY_ROLE)

        self.offsetPriority(selectedNode, offset)
        cmodel.sort(0, QtCore.Qt.DescendingOrder)

        with undoBlock():
            for node in selectedNode:
                priority = node.data(self._RENDERPRIORITY_ROLE)
                cmds.setAttr('.'.join([node.text(), "renderPriority"]), priority)

    def onPrioUpButtonClicked(self):
        cmodel = self.modelClone(self.__model)

        selectedId = sorted([i.row() for i
                             in self.UI.lineListView.selectedIndexes()])
        if not selectedId:
            return

        dst = cmodel.item(max(0, selectedId[0] - 1)).data(self._VIEWORDER_ROLE)
        for id in selectedId:
            sel = cmodel.item(id)
            order = sel.data(self._RENDERPRIORITY_ROLE)
            sel.setData(dst+order+1, self._VIEWORDER_ROLE)
            sel.setData(True, self._MODIFIED_ROLE)

        cmodel.sort(0, QtCore.Qt.DescendingOrder)

        modified = {}
        self.modelOptimizationAscent(cmodel, modified, check=False)
        self.modelOptimizationDescent(cmodel, modified, check=False)
        self.modelOptimizationAscent(cmodel, modified, check=True)

        with undoBlock():
            for node, priority in modified.items():
                cmds.setAttr("{}.renderPriority".format(node), priority)

    def onPrioDownButtonClicked(self):
        cmodel = self.modelClone(self.__model)
        selectedId = sorted([i.row() for i
                             in self.UI.lineListView.selectedIndexes()])
        if not selectedId:
            return

        dst = cmodel.item(min(cmodel.rowCount()-1, selectedId[-1]+1)).data(self._VIEWORDER_ROLE)
        for id in selectedId:
            sel = cmodel.item(id)
            order = sel.data(self._RENDERPRIORITY_ROLE)
            sel.setData(dst+order-100, self._VIEWORDER_ROLE)
            sel.setData(True, self._MODIFIED_ROLE)

        cmodel.sort(0, QtCore.Qt.DescendingOrder)

        modified = {}
        self.modelOptimizationDescent(cmodel, modified, check=False)
        self.modelOptimizationAscent(cmodel, modified, check=False)
        self.modelOptimizationDescent(cmodel, modified, check=True)

        with undoBlock():
            for node, priority in modified.items():
                cmds.setAttr("{}.renderPriority".format(node), priority)

    # レンダリング優先度テキストボックスの編集完了
    def onPriorityEditboxEdited(self):
        self.commitRenderPriority()

    # レンダリング優先度スライダーの操作
    def onPrioritySliderValueChanged(self, value):
        self.UI.renderPriorityEditBox.setText(str(value))

    # レンダリング優先度スライダーの操作(マウスボタンを離した時)
    def onPrioritySliderReleased(self):
        self.commitRenderPriority()

    ###
    # -----------------------------------------------------------------------------
    # Merge Groups
    # -----------------------------------------------------------------------------
    def onGroupsAddButtonClicked(self):
        # Update the contents of the UI list.
        self.refreshGroupsModel(False)
        current = set(self.__groupsModel.stringList())

        groups = GUI.getGroups()
        setsDeforms = GUI.getSetsWithDeformers()
        setsNoDeforms = GUI.excludeDeformersSets(setsDeforms)

        groupsDeforms = list(set(groups + setsDeforms) - current)
        groupsNoDeforms = list(set(groups + setsNoDeforms) - current)

        uiLabels = {
            'title': getLocalizedString("kWindowGroupsTitle"),
            'checkbox': getLocalizedString("kWindowGroupsCheckbox"),
            'buttonadd': getLocalizedString("kWindowGroupsButtonAdd"),
            'buttoncancel': getLocalizedString("kWindowGroupsButtonCancel"),
        }

        newGroups = AddItemsDialog.showWindow(uiLabels, False, groupsDeforms, groupsNoDeforms)

        self.addToGroups(newGroups)

    def onGroupsRemoveButtonClicked(self):
        for i in self.UI.groupsListView.selectionModel().selectedIndexes():
            name = i.data()
            if cmds.objExists(name):
                # Check if the item still exists in the scene.
                # Delete the merge group attribute
                cmds.deleteAttr(name, attribute=self._MERGE_GROUP_ATTRIBUTE_LONG_NAME)

        # Update the contents of the UI list to also remove groups that were
        # removed from the scene by the user at some other time.
        self.refreshGroupsModel(False)


    # 更新ボタンのクリック
    def onGroupsRefreshButtonClicked(self):
        self.refreshGroupsModel(True)

    # Applies the Maya selection to the Groups view.
    def groupsApplyMayaSelection(self):
        nodes = GUI.groupsGetNodes(True)
        GUI.selectDatas(self.UI.groupsListView, nodes)
        if len(nodes) > 0:
            self.setCurrentLine(self.UI.groupsListView, nodes[-1])

    # グループリスト内のアイテムがシーン内に存在しない場合はリストを更新する
    def adaptRefreshGroupsModel(self):
        for i in self.__groupsModel.stringList():
            if not cmds.objExists(i):
                self.refreshGroupsModel(True)

    def refreshGroupsModel(self, isRestoreSelection):
        # Save the selection state before clearing the UI
        selectedNodes = self.getCurrentSelectedData(self.UI.groupsListView)
        mainSel = self.getCurrentMainSelection(self.UI.groupsListView)

        nodeList = sortStrings(self.getMergeGroupTargets())
        self.__groupsModel.setStringList(nodeList)

        if isRestoreSelection:

            cleanSel = [node for node in selectedNodes if node in nodeList]
            if cleanSel:
                GUI.selectDatas(self.UI.groupsListView, cleanSel)
            if mainSel:
                self.setCurrentLine(self.UI.groupsListView, mainSel)

    def addToGroups(self, groups):
        if not groups:
            # Nothing to do if there was no selection
            return

        # At least 1 group was selected.
        groupsModel = self.__groupsModel
        current = groupsModel.stringList()
        current.extend(groups)
        groupsModel.setStringList(sortStrings(current))
        # Clear the current node selection
        cmds.select(clear=True)
        # Add the merge group attribute to the selected groups
        for g in groups:
            cmds.select(g, add=True, noExpand=True, hierarchy=False)
        # TODO Add category string?
        cmds.addAttr(longName=self._MERGE_GROUP_ATTRIBUTE_LONG_NAME,
                     shortName=self._MERGE_GROUP_ATTRIBUTE_SHORT_NAME,
                     attributeType=self._MERGE_GROUP_ATTRIBUTE_TYPE)

    # Returns a list of the names of the groups in the scene.
    # A group is a transform with no relatives that is not a joint.
    @staticmethod
    def getGroups(selectedOnly=False):
        groups = []
        trans = cmds.ls(type="transform", selection=selectedOnly)
        for t in trans:
            shapes = cmds.listRelatives(t, shapes=True)
            if not shapes and not cmds.joint(t, exists=t):
                groups.append(t)

        return groups

    # Maya2016以降ではshadingEngineが一緒にリストされるので除外する
    # Maya2015ではdefaultNodes引数が使えないので固定値
    __mayaDefaultSets = [item for item in cmds.ls(defaultNodes=True, type='objectSet') if cmds.nodeType(item) == 'objectSet'] \
                        if cmds.about(api=True) >= 201600 else ['defaultLightSet', 'defaultObjectSet']
    # Returns list of the names of the sets in the scene.
    # List including deformer sets.
    @staticmethod
    def getSetsWithDeformers(selectedOnly=False):
        # Always exclude Maya's default sets.
        # shadingEngineが一緒にリストされるので除外する
        return [item for item in cmds.ls(type='objectSet', selection=selectedOnly)
                    if cmds.nodeType(item) == 'objectSet' and item not in GUI.__mayaDefaultSets]

    # デフォーマーを取り除く
    @staticmethod
    def excludeDeformersSets(setsWithDeformers):
        return [item for item in setsWithDeformers if cmds.getAttr(item + '.usedBy', size=True) == 0]



    # Returns all the groups and sets in the scene that are Merge Group targets.
    @staticmethod
    def getMergeGroupTargets():
        groups = GUI.getGroups()
        setsDeforms = GUI.getSetsWithDeformers()

        def isMergeGroup(g):
            try:
                if cmds.attributeQuery(GUI._MERGE_GROUP_ATTRIBUTE_LONG_NAME, node=g, exists=True):
                    return True
            except TypeError:
                # Maya can return internal sets which cause this exception when queried,
                # so just ignore this error and don't include thoses sets in our targets.
                # Set name examples: internal_standInSE internal_soloSE initialParticleSE
                pass
            return False

        targets = [i for i in groups + setsDeforms if isMergeGroup(i)]
        return targets

    # Returns the Line nodes in the scene.
    # If selectedOnly is True, only the nodes selected in the Maya UI
    # are returned.
    # Order is the order from the command 'ls'.
    @staticmethod
    def groupsGetNodes(selectedOnly=False):
        return GUI.getGroups(True) + GUI.getSetsWithDeformers(True)

    ###
    # ------------------------------------------------------------------------------
    # Render Elements
    # ------------------------------------------------------------------------------

    # Extracts the name of the render element node from the list entry text
    @staticmethod
    def elemNameFromLineText(text):
        return text

    # Generates a list entry text from a node name and its type.
    @staticmethod
    def elemLineTextFromNode(nodeName, nodeType):
        return nodeName

    @staticmethod
    def elemType(nodeType):
        if nodeType =='PencilRenderElementsLine':
            return 'IMG'
        elif nodeType == 'PencilRenderElementsPld':
            return 'PLD'
        elif nodeType == 'PencilRenderElementsEps':
            return 'EPS'

        return ''


    # Returns the render elements nodes in the scene as a list of (node name, node type) tuples.
    # If selectedOnly is True, only the nodes selected in the Maya UI are returned.
    # Order is the order from the command 'ls'.
    @staticmethod
    def elemGetNodes(selectedOnly=False):
        types = ['PencilRenderElementsLine',
                 'PencilRenderElementsPld',
                 'PencilRenderElementsEps']

        lsCommandResult = cmds.ls(selection=selectedOnly, type=types, showType=True)
        return zip(*[iter(lsCommandResult)] * 2)

    # Displays the specified node in the Attribute Editor
    @staticmethod
    def elemRefreshAttributeEditor(nodeName):
        cmds.evalDeferred(
            '''import maya; maya.mel.eval(\'''
                if(`objExists {0}`) {{
                    if (!isAttributeEditorRaised()) showEditor {0};
                    else showEditorNoPopup {0};
                }}\''')
            '''.format(nodeName))

    # Applies the Maya selection to the render elements view.
    def elemApplyMayaSelection(self):
        nodeList = self.elemGetNodes(True)

        lines = []
        for nodeName, nodeType in nodeList:
            lineText = self.elemLineTextFromNode(nodeName, nodeType)
            lines.append(lineText)

        GUI.selectDatas(self.UI.elementsTreeView, lines)

        if len(lines) > 0:
            self.setCurrentLine(self.UI.elementsTreeView, lines[-1])

    # リストビューを更新する
    def refreshElemModel(self, isRestoreSelection):
        selectedLines = self.getCurrentSelectedData(self.UI.elementsTreeView)
        mainSel = self.getCurrentMainSelection(self.UI.elementsTreeView)
        nodeList = self.elemGetNodes(False)

        self.__renderElementModel.removeRows(0, self.__renderElementModel.rowCount())

        newLines = []
        for nodeName, nodeType in nodeList:
            isOn = cmds.getAttr(nodeName + ".on")

            lineText = self.elemLineTextFromNode(nodeName, nodeType)
            newLines.append(lineText)

            newItem = QtGui.QStandardItem(nodeName)
            etype = QtGui.QStandardItem(self.elemType(nodeType))
            newItem.setCheckable(True)
            if isOn:
                newItem.setCheckState(QtCore.Qt.Checked)
            else:
                newItem.setCheckState(QtCore.Qt.Unchecked)
            self.__renderElementModel.appendRow([newItem, etype])

        if isRestoreSelection:

            cleanSel = [line for line in selectedLines if line in newLines]
            if cleanSel:
                GUI.selectDatas(self.UI.elementsTreeView, cleanSel)
            if mainSel:
                self.setCurrentLine(self.UI.elementsTreeView, mainSel)
        self.UI.elementsTreeView.resizeColumnToContents(1)

    def elemFixSelection(self, selectedLines, activeIndex):
        self.fixSelection(self.UI.elementsTreeView, selectedLines, activeIndex)

        lineText = self.__renderElementModel.itemFromIndex(activeIndex).text()
        node = self.elemNameFromLineText(lineText)
        self.elemRefreshAttributeEditor(node)

    # Called when a line is clicked in the render elements list
    def onElemClicked(self, index):
        # Make sure that the clicked node is displayed in the attribute editor.
        # This gets called AFTER the current index has been changed, so we can't check
        # for a change in the current index to avoid refreshing the attribute editor 2 times
        # (here and in onElemCurrentChanged).
        currentElem = self.__renderElementModel.data(
                        self.__renderElementModel.index(index.row(), 0))
        node = self.elemNameFromLineText(currentElem)
        self.elemRefreshAttributeEditor(node)

    # Called when an item in the list of render elements changes (for instance, when checked / unchecked)
    def onElemItemChanged(self, item):
        lineText = item.text()
        # Find the new active value
        value = 1 if item.checkState() == QtCore.Qt.Checked else 0

        selectedLines = self.getCurrentSelectedData(self.UI.elementsTreeView)
        # Make the element that was checked / unchecked the active element
        activeIndex = self.__renderElementModel.indexFromItem(item)

        if lineText in selectedLines:
            # Apply the new state to every selected item
            with undoBlock():
                for l in selectedLines:
                    node = self.elemNameFromLineText(l)
                    cmds.setAttr(node + ".on", value)
        else:
            # Apply the new state to the node and select it in the list
            node = self.elemNameFromLineText(lineText)
            cmds.setAttr(node + ".on", value)
            selectedLines = [lineText]

        # The default selection behaviour when clicking checkboxes is strange.
        # We force a custom behaviour by re-selecting after the default selection completes.
        callback = partial(self.elemFixSelection, selectedLines=selectedLines, activeIndex=activeIndex)
        runDeferred(callback)

    # Handle changes to the current active line in the list
    def onElemCurrentChanged(self, current, prev):
        currentElem = self.__renderElementModel.data(current)
        if not currentElem:
            return

        node = self.elemNameFromLineText(currentElem)
        self.elemRefreshAttributeEditor(node)

    # "Add Render Element" ボタンのクリック
    def onElemAddButtonClicked(self):
        createdNode = mel.eval("shadingNode PencilRenderElementsLine -asUtility ")
        self.refreshElemModel(False)
        self.setCurrentLineAndSelectIt(self.UI.elementsTreeView, createdNode)

    # "Add EPS" ボタンのクリック
    def onEpsAddButtonClicked(self):
        createdNode = mel.eval("shadingNode PencilRenderElementsEps -asUtility")
        self.refreshElemModel(False)
        self.setCurrentLineAndSelectIt(self.UI.elementsTreeView, createdNode)

    # "Add Pld" ボタンのクリック
    def onPldAddButtonClicked(self):
        createdNode = cmds.shadingNode("PencilRenderElementsPld", asUtility=True)
        self.refreshElemModel(False)
        self.setCurrentLineAndSelectIt(self.UI.elementsTreeView, createdNode)

    # "Delete" ボタンのクリック
    def onElemDeleteButtonClicked(self):
        nodeNamesToDelete = []
        for index in self.UI.elementsTreeView.selectedIndexes():
            node = self.elemNameFromLineText(
                self.__renderElementModel.item(index.row(),0).text())
            nodeNamesToDelete.append(node)

        cmds.delete(nodeNamesToDelete)
        self.refreshElemModel(True)

    # 更新ボタンのクリック
    def onElemRefreshButtonClicked(self):
        self.refreshElemModel(True)
        selected = self.getCurrentSelectedData(self.UI.elementsTreeView)
#        if len(selected) > 0:
#            GUI.selectData(self.UI.elementsTreeView, selected[0])

    # Separate Foldersチェックボックスのクリック
    def onSeparateFoldersCheckBoxStateChanged(self, state):
        self.UI.separatorLabel.setEnabled(state != QtCore.Qt.Checked)
        self.UI.separatorComboBox.setEnabled(state != QtCore.Qt.Checked)

    # Set To All Nodesボタンのクリック
    def onSetToAllNodesButtonClicked(self):
        with undoBlock():
            for elem in cmds.ls(type=("PencilRenderElementsLine", "PencilRenderElementsPld", "PencilRenderElementsEps")):
                state = self.UI.separateFoldersCheckBox.checkState()
                cmds.setAttr(elem + ".separateFolders", 1 if state == QtCore.Qt.Checked else 0)
                cmds.setAttr(elem + ".separator", self.UI.separatorComboBox.currentIndex())


# ------------------------------------------------------------------------------
# Maya メインウィンドウの取得
def getMayaMainWindow():
    mainWindowPtr = OpenMayaUI.MQtUtil.mainWindow()
    return wrapInstance(long(mainWindowPtr), QtWidgets.QWidget)


# ------------------------------------------------------------------------------
# GUIの起動
def showWindow():
    app = QtWidgets.QApplication.instance()
    ui = GUI(getMayaMainWindow())
    ui.UI.addButton.setEnabled(True)
    ui.UI.deleteButton.setEnabled(True)
    ui.UI.refreshButton.setEnabled(True)
    ui.show()
    ui.activateWindow()
    #sys.exit()  # これは必要なのだろうか？
    app.exec_()

# ------------------------------------------------------------------------------
# GUIの終了（Pluginアンロード時にMELから呼び出される）
def destroyWindow():
    SingletonType.doDestroy(GUI)


# ------------------------------------------------------------------------------
# GUIのList更新 (C++側から呼び出される）
def refreshList():
    ui = GUI(getMayaMainWindow())
    utils.executeDeferred(lambda: ui.refreshList())
